package com.acme.bankapp.service.bank;

import com.acme.bankapp.domain.bank.Bank;
import com.acme.bankapp.domain.bank.account.Account;
import com.acme.bankapp.domain.bank.client.Client;
import com.acme.bankapp.domain.bank.client.ClientInfo;
import com.acme.bankapp.domain.bank.client.Gender;
import com.acme.bankapp.domain.bank.exception.*;
import com.acme.bankapp.service.bank.listener.ClientRegistrationListener;
import com.acme.bankapp.service.bank.utils.AccountInfoParser;
import com.acme.bankapp.service.bank.utils.ClientFactory;
import com.acme.bankapp.service.bank.utils.ParseAccountInfoException;

import java.io.*;
import java.util.Collection;
import java.util.HashSet;

/**
 * This class for manage {@link Bank} object
 *
 * @author Alexander Kotov
 */

public class BankService {

    private final Bank bank;

    public BankService(Bank bank) {
        assert (bank != null);
        if (bank == null) {
            throw new IllegalArgumentException("Null parameter bank");
        }
        this.bank = bank;
    }

    public BankService(BankLoader bankLoader) {
        this.bank = bankLoader.getBank();
    }

    public Bank getBank() {
        return bank;
    }

    /**
     * @return bank clients
     */
    public Collection<Client> getClients() {
        return bank.getClients();
    }

    /**
     * This method add client
     *
     * @param name     client's name
     * @param gender   client's gender
     * @param accounts client's accounts
     * @throws com.acme.bankapp.domain.bank.exception.ClientExistsException
     *
     */
    public void addClient(String name, Gender gender, String emailAddress, Account... accounts)
            throws ClientExistsException, AccountExistsException {
        Client client = new Client(name, gender, emailAddress, accounts);
        bank.addClient(client);
    }

    /**
     * This method withdraw and deposit random value on all bank clients accounts
     */
    public void modifyBank() {
        System.out.println("Bank modification");
        for (Client client : bank.getClients()) {
            for (Account account : client.getAccounts()) {
                try {
                    account.withdraw(Math.random());
                }
                catch (NotEnoughFundsException e) {
                    e.getMessage();
                }
                account.deposit(Math.random());
            }
        }
    }

    /**
     * This method print balance of all accounts for all clients
     */
    public void printBalance() {
        for (Client client : bank.getClients()) {
            for (Account account : client.getAccounts()) {
                System.out.format("%s %s%n", client, account);
            }
        }
    }

    /**
     * @return collection of account of client
     */
    public Collection<Account> getAccountsByClient(ClientInfo clientInfo) {
        return bank.getClientById(clientInfo).getAccounts();
    }

    /**
     * This method deposit amount on client's account
     *
     * @param clientInfo
     * @param accountId
     * @param amount
     */
    public void depositToClientAccount(ClientInfo clientInfo, int accountId, double amount)
            throws InvalidClientException, InvalidAccountException {
        Client client = bank.getClientById(clientInfo);
        if (client == null) {
            throw new InvalidClientException(clientInfo.toString());
        }
        Account account = client.getAccountById(accountId);
        if (account == null) {
            throw new InvalidAccountException(accountId, clientInfo.toString());
        }
        account.deposit(amount);
    }

    /**
     * This method withdraw amount from client's account
     *
     * @param clientInfo
     * @param accountId
     * @param amount
     */
    public void withdrawFromClientAccount(ClientInfo clientInfo, int accountId, double amount)
            throws InvalidClientException, InvalidAccountException {
        Client client = bank.getClientById(clientInfo);
        if (client == null) {
            throw new InvalidClientException(clientInfo.toString());
        }
        Account account = client.getAccountById(accountId);
        if (account == null) {
            throw new InvalidAccountException(accountId, clientInfo.toString());
        }
        try {
            account.withdraw(amount);
        } catch (NotEnoughFundsException e) {
            System.out.println(e.getMessage());
        }
    }

    /**
     * @return all clients accounts
     */
    public Collection<Account> getAccounts() {
        Collection<Account> collection = new HashSet<>();
        for (Client client : bank.getClients()) {
            for (Account account : client.getAccounts()) {
                collection.add(account);
            }
        }
        return collection;
    }

    /**
     * This method print maximum amount to withdraw for all clients
     */
    public void printMaxAmountToWithdraw() {
        for (Client client : bank.getClients()) {
            for (Account account : client.getAccounts()) {
                System.out.format("Maximum amount to withdraw for %s with account id %d is %f%n",
                        client, account.getId(), account.getMaximumAmountToWithdraw());
            }
        }
    }

    /**
     * This method add client registration listener to bank
     *
     * @param listener listener to add
     */
    public void addClientRegistrationListener(ClientRegistrationListener listener) {
        bank.addClientRegistrationListener(listener);
    }

    /**
     * This method load info about bank clients from file and adds these clients to the bank
     *
     * @param fileName
     * @throws com.acme.bankapp.service.bank.utils.ParseAccountInfoException
     * @throws IOException
     */
    public void loadClientsFromFile(final String fileName)
            throws ParseAccountInfoException, AccountExistsException, IOException {

        BufferedReader bufReader;
        String line;
        try {
            bufReader = new BufferedReader(new FileReader(fileName));
            while ((line = bufReader.readLine()) != null) {
                AccountInfoParser parser = new AccountInfoParser(line);
                Client client = ClientFactory.createClient(parser);
                Account account = ClientFactory.createAccount(parser);
                bank.addAccountForClient(client.getClientInfo(), account);
            }
        } catch (IOException e) {
            throw e;
        }
    }

    /**
     * This method serialize {@link Bank} object to file {@code fileName}
     *
     * @param fileName name of file to serialize
     * @throws IOException
     */
    public void saveBank(final String fileName)
            throws IOException {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName))) {
            oos.writeObject(bank);
        }
    }
}
